﻿using System.Collections.Generic;
using System.Threading.Tasks.Dataflow;
using NLog;
using QuantBox.QuantDo.Api;

namespace QuantBox.XApi
{
    internal class QdpDealProcessor
    {
        #region Order Event Class
        private static class OrderEventType
        {
            public const byte NewOrder = 0;
            public const byte OrderCancel = 1;
            public const byte OrderRsp = 2;
        }
        private abstract class OrderEvent
        {
            public abstract byte TypeId { get; }
        }
        private class NewOrderEvent : OrderEvent
        {
            public readonly OrderField Order;
            public NewOrderEvent(OrderField order)
            {
                Order = order;
            }
            public override byte TypeId => OrderEventType.NewOrder;
        }
        private class CancelOrderEvent : OrderEvent
        {
            public readonly string Id;

            public CancelOrderEvent(string id)
            {
                Id = id;
            }

            public override byte TypeId => OrderEventType.OrderCancel;
        }
        private class OrderRspEvent : OrderEvent
        {
            public readonly QdpResponse Response;

            public OrderRspEvent(QdpResponse response)
            {
                Response = response;
            }

            public override byte TypeId => OrderEventType.OrderRsp;
        }
        #endregion

        private readonly QdpTradeClient _client;
        private readonly ActionBlock<OrderEvent> _action;
        private readonly OrderMap _orders;
        private readonly HashSet<string> _tradeSet = new();
        private readonly HashSet<string> _cancelPending = new();
        private readonly List<QdpTrade> _tradePending = new();
        private readonly Logger _logger;

        private void ReportOrder(OrderField order, ExecType execType, OrderStatus newStatus, QdpRspInfo rspInfo)
        {
            ReportOrder(order, execType, newStatus, rspInfo.ErrorId, rspInfo.ErrorId, rspInfo.ErrorMsg);
        }

        private void ReportOrder(
            OrderField order,
            ExecType execType,
            OrderStatus newStatus,
            int errorId = 0, int rawErrorId = 0, string text = "")
        {
            order.Status = newStatus;
            var report = order.Clone();
            report.ExecType = execType;
            if (!string.IsNullOrEmpty(text))
            {
                report.XErrorID = errorId;
                report.RawErrorID = rawErrorId;
                report.SetText(text);
            }
            _client.spi.ProcessRtnOrder(report);
        }

        private void OrderAction(OrderEvent e)
        {
            switch (e.TypeId)
            {
                case OrderEventType.NewOrder:
                    ProcessNewOrder((NewOrderEvent)e);
                    return;
                case OrderEventType.OrderCancel:
                    ProcessCancelOrder((CancelOrderEvent)e);
                    return;
            }
            var rsp = ((OrderRspEvent)e).Response;
            switch (rsp.TypeId)
            {
                case QdpResponseType.OnErrRtnOrderAction:
                    ProcessCancelReject(rsp.Item1.AsOrderAction, rsp.Item2);
                    break;
                case QdpResponseType.OnErrRtnOrderInsert:
                    ProcessOrderReject(rsp.Item1.AsInputOrder, rsp.Item2);
                    break;
                case QdpResponseType.OnRspOrderAction:
                    ProcessCancelReject(rsp.Item1.AsOrderAction, rsp.Item2);
                    break;
                case QdpResponseType.OnRspOrderInsert:
                    ProcessOrderReject(rsp.Item1.AsInputOrder, rsp.Item2);
                    break;
                case QdpResponseType.OnRtnOrder:
                    ProcessRtnOrder(rsp.Item1.AsOrder);
                    break;
                case QdpResponseType.OnRtnTrade:
                    ProcessRtnTrade(rsp.Item1.AsTrade);
                    break;
            }
        }

        private void ProcessTradePending()
        {
            if (_tradePending.Count == 0)
            {
                return;
            }
            var list = new List<QdpTrade>(_tradePending);
            _tradePending.Clear();
            foreach (var trade in list)
            {
                ProcessRtnTrade(trade, false);
            }
        }

        private void ProcessRtnTrade(QdpTrade data, bool sendLog = true)
        {
            if (sendLog)
            {
                _client.spi.ProcessLog(new LogField(LogLevel.Trace, "RtnTrade"));
            }
            if (_orders.TryGetBySysId(data.OrderSysId, out var order) && order != null)
            {
                if (_tradeSet.Contains(data.TradeId))
                {
                    _logger.Warn($"重复的成交,oid:{data.OrderSysId},tid:{data.TradeId}");
                    return;
                }
                _tradeSet.Add(data.TradeId);

                _logger.Debug($"新成交,id:{order.ID},oid:{data.OrderSysId},tid:{data.TradeId}");
                var trade = QdpConvert.GetTrade(data);
                trade.ID = order.ID;
                _client.spi.ProcessRtnTrade(trade);
            }
            else
            {
                _logger.Debug($"成交暂存,oid:{data.OrderSysId},tid:{data.TradeId}");
                _tradePending.Add(data);
            }
        }

        private void ProcessRtnOrder(QdpOrder data)
        {
            var status = QdpConvert.GetOrderStatus(data);

            if (!_orders.TryGetById(data.UserCustom, out var order) || order == null)
            {
                _logger.Debug($"外部系统订单,oid:{data.OrderSysId},{status}");
                return;
            }

            if (order.OrderIsDone())
            {
                _logger.Debug($"更新已完成订单,oid:{order.OrderID},new:{status},old:{order.Status}");
                return;
            }

            _logger.Debug($"订单回报,id:{order.ID},oid:{data.OrderSysId},{status}");
            if (data.OrderSysId == order.OrderID && status == order.Status)
            {
                _logger.Debug($"订单状态重复,id:{order.ID},oid:{data.OrderSysId},{status}");
                return;
            }

            if (status == OrderStatus.Cancelled && order.ExecType != ExecType.PendingCancel)
            {
                _logger.Warn($"外部撤销,id:{order.ID},ref:{order.LocalID},oid:{data.OrderSysId},text:{order.Text()}");
            }

            var id = order.ID;
            if (!string.IsNullOrEmpty(data.OrderSysId) && string.IsNullOrEmpty(order.OrderID))
            {
                order.OrderID = data.OrderSysId;
                order.ExchangeID = data.ExchangeId;
                _orders.SetMap(order);
                ProcessTradePending();
            }
            ReportOrder(
                order,
                QdpConvert.GetExecType(data),
                QdpConvert.GetOrderStatus(data));

            if (_cancelPending.Contains(id))
            {
                _cancelPending.Remove(id);
                ProcessCancelOrder(id);
            }
        }

        #region Cancel Reject
        private void ProcessCancelReject(string orderSysId, QdpRspInfo rspInfo)
        {
            if (rspInfo.ErrorId != 0)
            {
                return;
            }
            if (_orders.TryGetBySysId(orderSysId, out var order) && order != null)
            {
                _logger.Debug($"撤单失败,id:{order.ID},oid:{order.OrderID},{rspInfo.ErrorMsg}");
            }
        }
        private void ProcessCancelReject(QdpOrderAction action, QdpRspInfo rspInfo)
        {
            ProcessCancelReject(action.OrderSysId, rspInfo);
        }
        #endregion

        #region Order Reject
        private void ProcessOrderReject(string id, QdpRspInfo rspInfo)
        {
            if (_orders.TryGetById(id, out var order))
            {
                if (rspInfo.ErrorMsg.Contains("该合约当前是连续交易暂停"))
                {
                    _logger.Debug($"订单警告,id:{order!.ID},{rspInfo.ErrorMsg}");
                    return;
                }
                _logger.Debug($"订单被拒,id:{order!.ID},{rspInfo.ErrorMsg}");
                ReportOrder(order, ExecType.Rejected, OrderStatus.Rejected, rspInfo);
            }
        }
        private void ProcessOrderReject(QdpInputOrder input, QdpRspInfo rspInfo)
        {
            if (rspInfo.ErrorId == 0)
            {
                return;
            }
            ProcessOrderReject(input.UserCustom, rspInfo);
        }
        #endregion

        private void ProcessCancelOrder(string id)
        {
            if (_cancelPending.Contains(id))
            {
                _logger.Warn($"重复撤单,id:{id}");
                return;
            }

            if (!_orders.TryGetById(id, out var order) || order == null)
            {
                _logger.Warn($"无效的订单ID,id:{id}");
                return;
            }

            if (string.IsNullOrEmpty(order.OrderID))
            {
                _logger.Debug($"撤单缓存,id:{id}");
                _cancelPending.Add(id);
                return;
            }

            _logger.Debug($"订单撤销,id:{id},oid:{order.OrderID}");
            var action = new QdpOrderAction();
            action.OrderSysId = order.OrderID;
            action.ExchangeId = order.ExchangeID.ToUpper();
            action.InstrumentId = order.InstrumentID;
            action.ActionFlag = QdpActionFlagType.Delete;
            action.UserOrderActionLocalId = _client.GetNextRequestId();
            action.InvestorId = _client.qdpLoginInfo!.UserId;
            action.BrokerId = _client.qdpLoginInfo.BrokerId;
            order.ExecType = ExecType.PendingCancel;
            _client.api!.ReqOrderAction(action, _client.GetNextRequestId());
        }

        private void ProcessCancelOrder(CancelOrderEvent e)
        {
            ProcessCancelOrder(e.Id);
        }

        private void ProcessNewOrder(NewOrderEvent e)
        {
            _orders.AddOrder(e.Order);
            if (e.Order.Status == OrderStatus.NotSent)
            {
                var data = QdpConvert.GetInputOrder(e.Order);
                //data.UserCustom = _client.userCustom;
                _logger.Debug($"订单发送,id:{e.Order.ID},ref:{e.Order.LocalID}");
                data.InvestorId = _client.qdpInvestor!.InvestorId;
                data.UserId = _client.qdpInvestor.UserId;
                data.BrokerId = _client.qdpInvestor.BrokerId;
                var ret = _client.api!.ReqOrderInsert(data, _client.GetNextRequestId());
                if (ret == 0)
                {
                    return;
                }
                ProcessOrderReject(data, new QdpRspInfo
                {
                    ErrorId = -1,
                    ErrorMsg = "连接中断发送失败"
                });
            }
        }

        public QdpDealProcessor(QdpTradeClient client)
        {
            _client = client;
            _logger = client.logger;
            _action = new ActionBlock<OrderEvent>(OrderAction);
            _orders = new OrderMap();
        }

        public void Post(OrderField order)
        {
            _action.Post(new NewOrderEvent(order));
        }

        public void Post(string id)
        {
            _action.Post(new CancelOrderEvent(id));
        }

        public void Post(QdpResponse rsp)
        {
            _action.Post(new OrderRspEvent(rsp));
        }

        public void Close()
        {
            _action.Complete();
            _action.Completion.Wait();
        }
    }
}
